package com.youshang.designpattern;

import lombok.Data;

import java.util.concurrent.atomic.AtomicReference;

@Data
class StackNode<T> {
    public T value;
    public StackNode<T> next;

    public StackNode(T value) {
        this.value = value;
    }

    public Boolean getValue() {
        return (Integer)value > 1;
    }
}

class BaselineLockFreeStack<T> {
    private AtomicReference<StackNode<T>> top = new AtomicReference<>();

    public void push(T value) {
        StackNode<T> newNode = new StackNode<>(value);
        StackNode<T> oldTop;
        do {
            oldTop = top.get();
            newNode.next = oldTop;
        } while (!top.compareAndSet(oldTop, newNode));
    }

    public T pop() {
        StackNode<T> oldTop;
        StackNode<T> newTop;
        do {
            oldTop = top.get();
            if (oldTop == null) return null;
            newTop = oldTop.next;
        } while (!top.compareAndSet(oldTop, newTop));
        return oldTop.value;
    }
}

class EliminationStack<T> {
    private AtomicReference<StackNode<T>> top = new AtomicReference<>();
    private Exchanger<T> exchanger;

    public EliminationStack(int eliminationArraySize, int maxDelay) {
        exchanger = new Exchanger<>(eliminationArraySize, maxDelay);
    }

    public void push(T value) {
        if (exchanger.tryExchange(value)) return;
        StackNode<T> newNode = new StackNode<>(value);
        StackNode<T> oldTop;
        do {
            oldTop = top.get();
            newNode.next = oldTop;
        } while (!top.compareAndSet(oldTop, newNode));
    }

    public Boolean pop() {
        Boolean value = exchanger.tryExchange(null);
        if (value != null) return value;
        StackNode<T> oldTop;
        StackNode<T> newTop;
        do {
            oldTop = top.get();
            if (oldTop == null) return null;
            newTop = oldTop.next;
        } while (!top.compareAndSet(oldTop, newTop));
        return oldTop.getValue();
    }
}

class Exchanger<T> {
    private final int eliminationArraySize;
    private final int maxDelay;
    private final AtomicReference<T>[] eliminationArray;

    public Exchanger(int eliminationArraySize, int maxDelay) {
        this.eliminationArraySize = eliminationArraySize;
        this.maxDelay = maxDelay;
        eliminationArray = (AtomicReference<T>[]) new AtomicReference[eliminationArraySize];
        for (int i = 0; i < eliminationArraySize; i++) {
            eliminationArray[i] = new AtomicReference<>();
        }
    }

    public boolean tryExchange(T value) {
        int slot = (int) (Math.random() * eliminationArraySize);
        int delay = 1;
        for (int i = 0; i < maxDelay; i += delay) {
            if (eliminationArray[slot].compareAndSet(null, value)) {
                for (int j = 0; j < i; j++) {
                    eliminationArray[slot].compareAndSet(value, null);
                }
                return true;
            }
            try {
                Thread.sleep(delay);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            delay *= 2;
        }
        return false;
    }
}

public class Main {
    public static void main(String[] args) {
        args = new String[]{"0","10","1000","10","1","1"};
        int x = Integer.parseInt(args[0]);
        int t = Integer.parseInt(args[1]);
        int n = Integer.parseInt(args[2]);
        int s = Integer.parseInt(args[3]);
        int e = Integer.parseInt(args[4]);
        int w = Integer.parseInt(args[5]);

        Runnable runnable;
        if (x == 0) {
            BaselineLockFreeStack<Integer> stack = new BaselineLockFreeStack<>();
            runnable = () -> {
                for (int i = 0; i < n; i++) {
                    if (Math.random() < 0.5) {
                        stack.push(i);
                    } else {
                        stack.pop();
                    }
                    try {
                        Thread.sleep((int) (Math.random() * s));
                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                    }
                }
            };
        } else {
            EliminationStack<Integer> stack = new EliminationStack<>(e, w);
            runnable = () -> {
                for (int i = 0; i < n; i++) {
                    if (Math.random() < 0.5) {
                        stack.push(i);
                    } else {
                        stack.pop();
                    }
                    try {
                        Thread.sleep((int) (Math.random() * s));
                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                    }
                }
            };
        }

        long startTime = System.currentTimeMillis();

        Thread[] threads = new Thread[t];
        for (int i = 0; i < t; i++) {
            threads[i] = new Thread(runnable);
            threads[i].start();
        }

        for (int i = 0; i < t; i++) {
            try {
                threads[i].join();
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
        }

        long endTime = System.currentTimeMillis();
        long elapsedTime = endTime - startTime;

        System.out.println("Elapsed Time: " + elapsedTime + " ms");
    }
}
